/*
 * @copyright Copyright (c) 2010 Laboratório de Educação Cerebral. (http://www.educacaocerebral.com.br)
 *
 * This file is part of SoftVFC.
 *
 * SoftVFC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SoftVFC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SoftVFC.  If not, see <http://www.gnu.org/licenses/>.
 *
 * *********************
 *
 * Este arquivo é parte do programa SoftVFC.
 *
 * SoftVFC é um software livre; você pode redistribui-lo e/ou
 * modifica-lo dentro dos termos da Licença Pública Geral GNU como
 * publicada pela Fundação do Software Livre (FSF); na versão 3 da
 * Licença.
 *
 * Este programa é distribuido na esperança que possa ser util,
 * mas SEM NENHUMA GARANTIA; sem uma garantia implicita de ADEQUAÇÂO a qualquer
 * MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a
 * Licença Pública Geral GNU para maiores detalhes.
 *
 * Você deve ter recebido uma cópia da Licença Pública Geral GNU
 * junto com este programa, se não, acesse no website oficial:
 * http://www.gnu.org/licenses/gpl.html
 *
 */
package org.softvfc.mixer.aux;

import java.util.ArrayList;
import java.util.List;
import java.awt.BorderLayout;
import java.util.Hashtable;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.BoundedRangeModel;
import javax.swing.BoxLayout;
import javax.swing.ButtonModel;
import javax.swing.JComponent;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTabbedPane; 
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.CompoundControl;
import javax.sound.sampled.Control;
import javax.sound.sampled.EnumControl;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Port;
import javax.swing.JLabel;

/**
 *
 * @author Diego Schmaedech Martins (schmaedech@gmail.com)
 * @version 29/07/2010
 */
public class SystemMixerEngine{
     
    private static final Port.Info[] EMPTY_PORT_INFO_ARRAY = new Port.Info[0];

    public SystemMixerEngine(){
        
    }

    public List<Mixer> getPortMixers(){
        List<Mixer> supportingMixers = new ArrayList<Mixer>();
        Mixer.Info[] aMixerInfos = AudioSystem.getMixerInfo();
        //Line.Info portInfo = new Line.Info(Port.class);
        for (int i = 0; i < aMixerInfos.length; i++){
            Mixer mixer = AudioSystem.getMixer(aMixerInfos[i]);
            boolean bSupportsPorts = arePortsSupported(mixer);
            if (bSupportsPorts){
                supportingMixers.add(mixer);
            }
        }
        return supportingMixers;
    }

    private boolean arePortsSupported(Mixer mixer){
        Line.Info[] infos;
        infos = mixer.getSourceLineInfo();
        for (int i = 0; i < infos.length; i++){
            if (infos[i] instanceof Port.Info){
                return true;
            }
        }
        infos = mixer.getTargetLineInfo();
        for (int i = 0; i < infos.length; i++)	{
            if (infos[i] instanceof Port.Info){
                return true;
            }
        }
        return false;
    }

    private Port.Info[] getPortInfo(Mixer mixer){
        Line.Info[] infos;
        List<Port.Info> portInfoList = new ArrayList<Port.Info>();
        infos = mixer.getSourceLineInfo();
        for (int i = 0; i < infos.length; i++){
            if (infos[i] instanceof Port.Info){
                portInfoList.add((Port.Info) infos[i]);
            }
        }
        infos = mixer.getTargetLineInfo();
        for (int i = 0; i < infos.length; i++){
            if (infos[i] instanceof Port.Info){
                portInfoList.add((Port.Info) infos[i]);
            }
        }
        Port.Info[] portInfos = portInfoList.toArray(EMPTY_PORT_INFO_ARRAY);
        return portInfos;
    }
 
    public JTabbedPane createMixerPanel(Mixer mixer){
        JTabbedPane mixerPanel = new JTabbedPane();
        //mixerPanel.setLayout(new BoxLayout(mixerPanel, BoxLayout.X_AXIS));
        Port.Info[] infosToCheck = getPortInfo(mixer);
        for (int i = 0; i < infosToCheck.length; i++){
            if (mixer.isLineSupported(infosToCheck[i])) {
                Port port = null;
                try{
                    port = (Port) mixer.getLine(infosToCheck[i]);
                    port.open();
                }catch (LineUnavailableException ex){

                }
                if (port != null){
                    JPanel portPanel = createPortPanel(port);
                    String strPortName = ((Port.Info) port.getLineInfo()).getName();
                    mixerPanel.addTab(strPortName, portPanel);
                }
            }
        }
        return mixerPanel;
    }

    private JPanel createPortPanel(Port port){
        JPanel portPanel = new JPanel();
        JPanel controlsPanelCenter = new JPanel();
        JPanel controlsPanelSouth = new JPanel(); 
        portPanel.add(controlsPanelCenter, BorderLayout.CENTER);
        portPanel.add(controlsPanelSouth, BorderLayout.SOUTH);

        Control[] aControls = port.getControls(); 
//        final CompoundControl cc = (CompoundControl)port.getControls()[0];
//        final Control[] aControls = cc.getMemberControls();

        for (int i = 0; i < aControls.length; i++){

            JComponent controlComponent = createControlComponent(aControls[i]);
            if (aControls[i] instanceof FloatControl){
                controlsPanelCenter.add(controlComponent);
            }else{
                controlsPanelSouth.add(controlComponent);
            }
        }
        return portPanel;
    }

    private JComponent createControlComponent(Control control){
        JComponent component = null;
        if (control instanceof BooleanControl){
            component = createControlComponent((BooleanControl) control);
        }else if (control instanceof EnumControl){
            component = createControlComponent((EnumControl) control);
        }else if (control instanceof FloatControl){
            component = createControlComponent((FloatControl) control);
        }else if (control instanceof CompoundControl){
            component = createControlComponent((CompoundControl) control);
        }
        return component;
    }

    private JComponent createControlComponent(BooleanControl control){
        AbstractButton button;
        String strControlName = control.getType().toString();
        ButtonModel model = new BooleanControlButtonModel(control);
        button = new JCheckBox(strControlName);
        button.setModel(model);
        
        return button;
    }

    private JComponent createControlComponent(EnumControl control){
        JPanel component = new JPanel();
        String strControlName = control.getType().toString();         
        component.setBorder(javax.swing.BorderFactory.createTitledBorder(null, strControlName, javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 10), new java.awt.Color(1, 1, 1)));
        System.out.println("WARNING: EnumControl is not yet supported");
        return component;
    }

    private JComponent createControlComponent(FloatControl control){
        Control.Type type = control.getType();
        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new BorderLayout());
        String strControlName = type.toString(); 
        controlPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(null, strControlName, javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 10), new java.awt.Color(1, 1, 1)));
        int orientation = isBalanceOrPan(control) ? JSlider.HORIZONTAL : JSlider.VERTICAL;
        BoundedRangeModel model = new FloatControlBoundedRangeModel(control);

        JSlider slider = new JSlider(model);
        Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
        JLabel max = new JLabel("max");
        //JLabel def = new JLabel("def");
        JLabel min = new JLabel("min");


        labelTable.put(model.getMaximum(), max);
        //labelTable.put( model.getValue(), def );
        labelTable.put( model.getMinimum(), min );
        slider.setLabelTable( labelTable );
        slider.setOrientation(orientation);
        slider.setPaintLabels(true);
        slider.setPaintTicks(true);
        setTickSpacings(slider);
        controlPanel.add(slider, BorderLayout.CENTER);
        return controlPanel;
    }

    private JComponent createControlComponent(CompoundControl control){
        JComponent controlPanel = new Box(BoxLayout.Y_AXIS);
        // for BooleanControl and EnumControl
        JComponent booleanPanel = new Box(BoxLayout.Y_AXIS);
        // for FloatControl, BALANCE and PAN
        JComponent balancePanel = new Box(BoxLayout.Y_AXIS);
        // for FloatControl, all other types
        JComponent volumePanel = new Box(BoxLayout.X_AXIS);

        controlPanel.add(booleanPanel);
        controlPanel.add(balancePanel);
        controlPanel.add(volumePanel);

        //String	strControlName = control.getType().toString();
        //controlPanel.setBorder(new TitledBorder(new EtchedBorder(), strControlName));
        Control[] aControls = control.getMemberControls();
        for (int i = 0; i < aControls.length; i++){
            Control con = aControls[i];
            JComponent member = createControlComponent(con);
            if (con instanceof BooleanControl ||con instanceof EnumControl){
                booleanPanel.add(member);
            }else if (con instanceof FloatControl){
                if (isBalanceOrPan((FloatControl) con))	{
                    balancePanel.add(member);
                }else{
                    if(isMasterGain((FloatControl) con)){
                        
                    }else{
                         volumePanel.add(member);
                    }
                   
                }

            }else{
                controlPanel.add(member);
            }
        }
        return controlPanel;
    }

    private void setTickSpacings(JSlider slider){
        int nRange = slider.getMaximum() - slider.getMinimum();
        if (nRange < 10){
            slider.setMajorTickSpacing(1);
        }else if (nRange < 50){
            slider.setMajorTickSpacing(5);
                // slider.setMinorTickSpacing(1);
        }else if (nRange < 100){
            slider.setMajorTickSpacing(10);
                // slider.setMinorTickSpacing(5);
        }else if (nRange < 200){
            slider.setMajorTickSpacing(20);
                // slider.setMinorTickSpacing(5);
        }else if (nRange < 500){
            slider.setMajorTickSpacing(50);
                // slider.setMinorTickSpacing(10);
        }else{
            slider.setMajorTickSpacing(100);
                // slider.setMinorTickSpacing(100);
        }
    }

    private static boolean isBalanceOrPan(FloatControl control){
        Control.Type type = control.getType();
        return type.equals(FloatControl.Type.PAN) || type.equals(FloatControl.Type.BALANCE);
    }

    private static boolean isMasterGain(FloatControl control){
        Control.Type type = control.getType();
        return type.equals(FloatControl.Type.MASTER_GAIN);
    }

    public void setDeviceSensitivity(final int sensitivity, Mixer.Info info){
        final Port lineIn;
        final Mixer mixer = AudioSystem.getMixer(info);
        try{
            if(mixer.isLineSupported(Port.Info.LINE_IN)){
                lineIn = (Port)mixer.getLine(Port.Info.LINE_IN);
                lineIn.open();
            }else if(mixer.isLineSupported(Port.Info.MICROPHONE)){
                lineIn = (Port)mixer.getLine(Port.Info.MICROPHONE);
                lineIn.open();
            }
            else{
                
                return;
            }
            lineIn.getControls();

            if(lineIn.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                 
            }

            final CompoundControl cc = (CompoundControl)lineIn.getControls()[0];
            final Control[] controls = cc.getMemberControls();
            for(final Control c : controls)	{
                if(c instanceof FloatControl){
                    ((FloatControl)c).setValue(((FloatControl)c).getMaximum());
                }
            }
        }
        catch(final Exception ex){
             
        }
    }

}

 